#include <string.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

#include <psp2/system_param.h>
#include <psp2/io/fcntl.h>

#include <vita2d.h>

#include "browser.h"
#include "ui.h"
#include "main.h"
#include "utils.h"
#include "file.h"
#include "menu.h"
#include "config.h"
#include "about.h"
#include "retro.h"
#include "states.h"
#include "lang.h"

#define VIEW_MARGIN 2.0f

#define PATH_PADDING_T 6.0f
#define PATH_PADDING_L 10.0f
#define PATH_TEXT_COLOR SPRING_GREEN

#define NAME_PADDING_T 10.0f
#define NAME_PADDING_L 10.0f
#define NAME_TEXT_LINE_SPACE 9.0f

#define FOLDER_TEXT_COLOR WHITE
#define FILE_TEXT_COLOR SPRING_GREEN
#define NAME_TEXT_COLOR_FOCUS_BG COLOR_ALPHA(AZURE, 0x8F)

#define PREVIEW_PADDING 8.0f
#define PREVIEW_LOAD_WAIT_COUNT 6.0f

InstructionsEntry browser_instructions_entries[] = {
    {{STR_BUTTON_CANCEL, NULL}, STR_PARENT_DIRECTORY},
    {{STR_BUTTON_ENTER, NULL}, STR_OPEN},
    {{STR_BUTTON_PSBUTTON, NULL}, STR_EMU_OPTIONS},
    {{STR_BUTTON_SELECT, NULL}, STR_ABOUT},
    {{STR_BUTTON_START, NULL}, STR_CHANGE_DIRECTORY},
    {{0}, NULL},
};

static void setFocusOnFilename(const char *name);
static void browserRefreshListPos();
static int refreshFileList();
static void browserHandleFolder(FileListEntry *file_entry);
static void browserHandleFile(FileListEntry *file_entry);

// File lists
static FileList file_list;

static float preview_x, preview_y;
static float preview_scale_x, preview_scale_y;

// Position
static int entries_top_pos = 0, entries_focus_pos = 0, entries_old_focus_pos = 0;
static int top_pos_list[MAX_DIR_LEVELS];
static int focus_pos_list[MAX_DIR_LEVELS];
static int dir_level = 0;

// Modes
int sort_mode = SORT_BY_NAME;
int old_sort_mode = SORT_BY_NAME;

static vita2d_texture *preview_tex = NULL;
static int preview_need_refresh = 1;
static int preview_refresh_wait = 0;
static int old_preview_path = 0;
static int old_preview_style = PREVIEW_PRESERVE_FILL;
static int old_auto_save_load = 0;

// Scrolling filename
static int scroll_count = 0;
static float name_scroll_x;
static float scroll_bar_x, scroll_bar_y, scroll_bar_height;

static float path_view_sx, path_view_sy, path_text_sx, path_text_sy;
static float path_view_width, path_view_height, path_text_width;

static int name_draw_lines;

static float name_view_sx, name_view_sy, name_text_sx, name_text_sy;
static float name_view_width, name_view_height, name_text_width, name_text_y_space;

static float preview_view_sx, preview_view_sy, preview_sx, preview_sy;
static float preview_view_width, preview_view_height, preview_width, preview_height;

void initBrowserDrawInfo()
{
    old_preview_style = setting_config.preview_style;

    float line_height = UiGetLineHeight();

    path_view_width = MAIN_FREE_DRAW_WIDTH;
    path_view_height = line_height + PATH_PADDING_T * 2;
    path_view_sx = MAIN_FREE_DRAW_SX;
    path_view_sy = MAIN_FREE_DRAW_SY;
    path_text_sx = path_view_sx + PATH_PADDING_L;
    path_text_sy = path_view_sy + PATH_PADDING_T;
    path_text_width = path_view_width - PATH_PADDING_L * 2;

    preview_view_height = MAIN_FREE_DRAW_HEIGHT - path_view_height - VIEW_MARGIN;
    preview_view_width = preview_view_height;
    preview_view_sx = MAIN_FREE_DRAW_DX - preview_view_width;
    preview_view_sy = MAIN_FREE_DRAW_DY - preview_view_height;
    preview_width = preview_view_width - PREVIEW_PADDING * 2;
    preview_height = preview_width;
    preview_sx = preview_view_sx + PREVIEW_PADDING;
    preview_sy = preview_view_sy + PREVIEW_PADDING;

    name_view_width = MAIN_FREE_DRAW_WIDTH - preview_view_width - VIEW_MARGIN;
    name_view_height = preview_view_height;
    name_view_sx = path_view_sx;
    name_view_sy = preview_view_sy;

    float name_text_full_height = name_view_height - NAME_PADDING_T * 2;
    name_text_y_space = line_height + NAME_TEXT_LINE_SPACE;
    name_draw_lines = (name_text_full_height + NAME_TEXT_LINE_SPACE) / name_text_y_space;

    name_text_sx = name_view_sx + NAME_PADDING_L;
    name_text_sy = name_view_sy + NAME_PADDING_T;
    name_text_width = name_view_width - NAME_PADDING_L * 2;
    name_scroll_x = name_text_sx;

    scroll_bar_x = name_view_sx + name_view_width - SCROLL_BAR_WIDTH;
    scroll_bar_y = name_view_sy;
    scroll_bar_height = name_view_height;
}

int currentPathIsFile()
{
    FileListEntry *entry = fileListGetEntryByNumber(&file_list, entries_focus_pos);
    if (!entry)
        return 0;
    if (!entry->is_folder)
        return 1;

    return 0;
}

int makeCurFileName(char *name)
{
    FileListEntry *entry = fileListGetEntryByNumber(&file_list, entries_focus_pos);
    if (!entry)
        goto failed;

    snprintf(name, MAX_NAME_LENGTH, entry->name);

    return 0;

failed:
    name[0] = '\0';
    return -1;
}

int makeCurFilePath(char *path)
{
    FileListEntry *entry = fileListGetEntryByNumber(&file_list, entries_focus_pos);
    if (!entry)
        goto failed;

    snprintf(path, MAX_PATH_LENGTH, "%s%s", file_list.path, entry->name);

    return 0;

failed:
    path[0] = '\0';
    return -1;
}

int makePreviewPath(char *path)
{
    FileListEntry *entry = fileListGetEntryByNumber(&file_list, entries_focus_pos);
    if (!entry)
        goto failed;

    char base_name[MAX_NAME_LENGTH];
    int ret = makeBaseName(base_name, entry->name, MAX_NAME_LENGTH);
    if (ret < 0)
        goto failed;

    snprintf(path, MAX_PATH_LENGTH, ("%s/%s/%s.png"), file_list.path, (PREVIEW_DIR_NAME), base_name);

    return 0;

failed:
    path[0] = '\0';
    return -1;
}

static vita2d_texture *loadDefaultPreview()
{
    char path[MAX_PATH_LENGTH];
    makePreviewPath(path);
    return vita2d_load_PNG_file(path);
}

int loadPreview()
{
    if (preview_tex)
    {
        vita2d_wait_rendering_done();
        vita2d_free_texture(preview_tex);
        preview_tex = NULL;
    }
    if (setting_config.preview_path == 0)
    {
        if (misc_config.auto_save_load)
            preview_tex = getTextureFromSavestate(-1);
        if (!preview_tex)
        {
            preview_tex = loadDefaultPreview();
            if (!preview_tex && !misc_config.auto_save_load)
                preview_tex = getTextureFromSavestate(-1);
        }
    }
    else if (setting_config.preview_path == 2)
    {
        preview_tex = getTextureFromSavestate(-1);
    }
    else if (setting_config.preview_path == 1)
    {
        preview_tex = loadDefaultPreview();
    }

    if (preview_tex)
    {
        float tex_width = (float)vita2d_texture_get_width(preview_tex);
        float tex_height = (float)vita2d_texture_get_height(preview_tex);
        float width = preview_width;
        float height = preview_height;
        if (setting_config.preview_style == PREVIEW_PRESERVE_FILL)
        {
            height = tex_height * (width / tex_width);
            if (height > preview_height)
            {
                height = preview_height;
                width = tex_width * (height / tex_height);
            }
        }

        preview_scale_x = width / tex_width;
        preview_scale_y = height / tex_height;
        preview_x = preview_sx + (preview_width - width) / 2;
        preview_y = preview_sy + (preview_height - height) / 2;
    }
    preview_need_refresh = 0;

    return 0;
}

int unloadPreview()
{
    if (preview_tex)
    {
        vita2d_wait_rendering_done();
        vita2d_free_texture(preview_tex);
        preview_tex = NULL;
    }
    preview_need_refresh = 1;

    return 0;
}

static void dirLevelUp()
{
    if (dir_level < MAX_DIR_LEVELS - 1)
    {
        top_pos_list[dir_level] = entries_top_pos;
        focus_pos_list[dir_level] = entries_focus_pos;
        dir_level++;
        top_pos_list[dir_level] = 0;
        focus_pos_list[dir_level] = 0;
    }

    entries_top_pos = 0;
    entries_focus_pos = 0;
}

static void dirUp()
{
    removeEndSlash(file_list.path);

    char *p;
    p = strrchr(file_list.path, '/');
    if (p)
    {
        p[1] = '\0';
        dir_level--;
        goto DIR_UP_RETURN;
    }

    p = strrchr(file_list.path, ':');
    if (p)
    {
        if (strlen(file_list.path) - ((p + 1) - file_list.path) > 0)
        {
            p[1] = '\0';
            dir_level--;
            goto DIR_UP_RETURN;
        }
    }

    strcpy(file_list.path, HOME_PATH);
    dir_level = 0;

DIR_UP_RETURN:
    if (dir_level < 0)
        dir_level = 0;

    entries_top_pos = top_pos_list[dir_level];
    entries_focus_pos = focus_pos_list[dir_level];
}

static void setFocusOnFilename(const char *name)
{
    int name_pos = fileListGetNumberByName(&file_list, name);
    if (name_pos < 0 || name_pos >= file_list.length)
        return;

    entries_focus_pos = name_pos;
    browserRefreshListPos(LIST_CONTROL_NONE, entries_focus_pos);
}

static int changeToDirectory(char *lastdir)
{
    if (!checkFolderExist(lastdir))
        return -1;

    dir_level = 0;
    strcpy(file_list.path, HOME_PATH);
    int i;
    for (i = 0; i < strlen(lastdir) + 1; i++)
    {
        if (lastdir[i] == ':' || lastdir[i] == '/')
        {
            char ch = lastdir[i + 1];
            lastdir[i + 1] = '\0';

            char ch2 = lastdir[i];
            lastdir[i] = '\0';

            char *p = strrchr(lastdir, '/');
            if (!p)
                p = strrchr(lastdir, ':');
            if (!p)
                p = lastdir - 1;

            lastdir[i] = ch2;

            refreshFileList();
            setFocusOnFilename(p + 1);

            strcpy(file_list.path, lastdir);

            lastdir[i + 1] = ch;

            dirLevelUp();
        }
    }
    refreshFileList();

    return 0;
}

int changeToDirectoryFromPath(const char *path)
{
    int ret;

    char lastdir[MAX_PATH_LENGTH];
    ret = makeBaseDirectory(lastdir, path, MAX_PATH_LENGTH);
    if (ret < 0)
        return ret;

    ret = changeToDirectory(lastdir);
    if (ret < 0)
        return ret;

    char name[MAX_NAME_LENGTH];
    ret = makeFilename(name, path, MAX_NAME_LENGTH);
    if (ret >= 0)
        setFocusOnFilename(name);

    return 0;
}

int changeToDirectoryFromFile(const char *path)
{
    char lastfile[MAX_PATH_LENGTH];
    if (ReadFile((path), lastfile, sizeof(lastfile)) <= 0)
        return -1;

    return changeToDirectoryFromPath(lastfile);
}

void browserRefreshListPos(int type, int pos)
{
    controlRefreshListPos(type, &entries_top_pos, &pos, file_list.length, name_draw_lines);
    entries_focus_pos = pos;

    if (entries_focus_pos != entries_old_focus_pos)
    {
        scroll_count = 0;
        unloadPreview();
        preview_refresh_wait = 0;
        entries_old_focus_pos = entries_focus_pos;
        setStatesFocusPos(0);
    }
}

static int refreshFileList()
{
    int ret = 0, res = 0;
    sort_mode = old_sort_mode;

    do
    {
        fileListEmpty(&file_list);

        res = fileListGetEntries(&file_list, file_list.path, sort_mode);

        if (res < 0)
        {
            ret = res;
            dirUp();
        }
    } while (res < 0);

    unloadPreview();
    browserRefreshListPos(LIST_CONTROL_NONE, entries_focus_pos);

    return ret;
}

static void browserHandleFile(FileListEntry *file_entry)
{
    char path[MAX_PATH_LENGTH];
    makeCurFilePath(path);
    if (!loadGame(path))
        return;
    if (misc_config.auto_save_load)
        loadState(-1);
}

static void browserHandleFolder(FileListEntry *file_entry)
{
    if (dir_level == 0)
    {
        strcpy(file_list.path, file_entry->name);
    }
    else
    {
        if (dir_level > 1)
            addEndSlash(file_list.path);
        strcat(file_list.path, file_entry->name);
    }
    dirLevelUp();

    refreshFileList();
}

int initBrowser()
{
    memset(top_pos_list, 0, sizeof(top_pos_list));
    memset(focus_pos_list, 0, sizeof(focus_pos_list));
    memset(&file_list, 0, sizeof(FileList));

    strcpy(file_list.path, HOME_PATH);

    changeToDirectoryFromFile(LASTFILE_PATH);

    refreshFileList();

    return 0;
}

int drawBrowser()
{
    if (old_preview_style != setting_config.preview_style || old_preview_path != setting_config.preview_path
        || (setting_config.preview_path == 0 && old_auto_save_load != misc_config.auto_save_load))
    {
        unloadPreview();
        old_preview_path = setting_config.preview_path;
        old_preview_style = setting_config.preview_style;
        old_auto_save_load = misc_config.auto_save_load;
    }

    float line_height = UiGetLineHeight();

    vita2d_draw_rectangle(path_view_sx, path_view_sy, path_view_width, path_view_height, DEFALUT_BG_COLOR);
    vita2d_draw_rectangle(name_view_sx, name_view_sy, name_view_width, name_view_height, DEFALUT_BG_COLOR);
    vita2d_draw_rectangle(preview_view_sx, preview_view_sy, preview_view_width, preview_view_height, DEFALUT_BG_COLOR);

    // Draw path
    vita2d_enable_clipping();
    vita2d_set_clip_rectangle(path_text_sx, path_text_sy, path_text_sx + path_text_width, path_text_sy + line_height);
    UiDrawText(path_text_sx, path_text_sy, PATH_TEXT_COLOR, file_list.path);
    vita2d_disable_clipping();

    // Draw FileListEntry
    FileListEntry *file_entry = fileListGetEntryByNumber(&file_list, entries_top_pos);
    FileListEntry *focus_entry = NULL;
    if (file_entry)
    {
        int i;
        float sx = name_text_sx;
        float sy = name_text_sy;
        int drawn_lines = 0;
        for (i = entries_top_pos; i < file_list.length; i++)
        {
            if (drawn_lines >= name_draw_lines)
                break;

            uint32_t color;
            if (file_entry->is_folder)
                color = FOLDER_TEXT_COLOR;
            else
                color = FILE_TEXT_COLOR;

            // Draw file name
            sx = name_text_sx;

            char *file_name = file_entry->name;

            // Current position
            if (i == entries_focus_pos)
            {
                focus_entry = file_entry;
                vita2d_draw_rectangle(name_view_sx, sy - NAME_TEXT_LINE_SPACE / 2, name_view_width, name_text_y_space, NAME_TEXT_COLOR_FOCUS_BG);

                int width = UiGetTextWidth(file_name);
                if (width >= (int)name_text_width)
                {
                    if (scroll_count < 60)
                    {
                        name_scroll_x = sx;
                    }
                    else if (scroll_count < width + 90)
                    {
                        name_scroll_x--;
                    }
                    else if (scroll_count < width + 120)
                    {
                        color = (color & 0x00FFFFFF) | ((((color >> 24) * (scroll_count - width - 90)) / 30) << 24); // fade-in in 0.5s
                        name_scroll_x = sx;
                    }
                    else
                    {
                        scroll_count = 0;
                    }

                    scroll_count++;

                    sx = name_scroll_x;
                }
            }

            vita2d_enable_clipping();
            vita2d_set_clip_rectangle(name_text_sx, sy, name_text_sx + name_text_width, sy + line_height);
            UiDrawText(sx, sy, color, file_name);
            vita2d_disable_clipping();

            // Next
            file_entry = file_entry->next;
            sy += name_text_y_space;
            drawn_lines++;
        }
        drawScrollBar(scroll_bar_x, scroll_bar_y, scroll_bar_height, name_draw_lines, file_list.length, entries_top_pos);
    }

    if (focus_entry && !focus_entry->is_folder && preview_need_refresh && preview_refresh_wait >= PREVIEW_LOAD_WAIT_COUNT)
        loadPreview();

    if (focus_entry && !focus_entry->is_folder && !preview_need_refresh && preview_tex)
        vita2d_draw_texture_scale(preview_tex, preview_x, preview_y, preview_scale_x, preview_scale_y);

    if (preview_refresh_wait < PREVIEW_LOAD_WAIT_COUNT)
        preview_refresh_wait++;

    return 0;
}

int ctrlBrowser()
{
    // Move
    if (hold_pad[PAD_UP] || hold2_pad[PAD_LEFT_ANALOG_UP])
    {
        browserRefreshListPos(LIST_CONTROL_UP, entries_focus_pos);
    }
    else if (hold_pad[PAD_DOWN] || hold2_pad[PAD_LEFT_ANALOG_DOWN])
    {
        browserRefreshListPos(LIST_CONTROL_DOWN, entries_focus_pos);
    }
    else if (hold_pad[PAD_LEFT])
    {
        browserRefreshListPos(LIST_CONTROL_LEFT, entries_focus_pos);
    }
    else if (hold_pad[PAD_RIGHT])
    {
        browserRefreshListPos(LIST_CONTROL_RIGHT, entries_focus_pos);
    }

    // Open menu
    if (pressed_pad[PAD_TRIANGLE])
    {
    }

    // Back
    if (pressed_pad[PAD_CANCEL])
    {
        // Not at 'home'
        if (dir_level > 0)
        {
            scroll_count = 0;
            dirUp();
            refreshFileList();
        }
    }

    // Handle
    if (pressed_pad[PAD_ENTER])
    {
        scroll_count = 0;

        // Handle file or folder
        FileListEntry *file_entry = fileListGetEntryByNumber(&file_list, entries_focus_pos);
        if (file_entry)
        {
            if (file_entry->is_folder)
            {
                browserHandleFolder(file_entry);
            }
            else
            {
                browserHandleFile(file_entry);
            }
        }
    }

    if (pressed_pad[PAD_SELECT])
    {
        enterAbout();
    }

    if (pressed_pad[PAD_START])
    {
        changeToDirectoryFromFile(LASTFILE_PATH);
    }

    return 0;
}
